﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using Tesla.Graphics;

namespace Tesla.Core {
    /// <summary>
    /// Static root class that manages key engine services (notably the render system) that are used to serve
    /// many engine objects. This class provides a central point to initialize rendering services and then to clean up resources.
    /// </summary>
    public static class Engine {

        private static EngineServiceProviders _providers = new EngineServiceProviders();
        private static EngineValueMap _valueMap = new EngineValueMap();
        private static Type _renderSystemType = typeof(IRenderSystemProvider);

        /// <summary>
        /// Event for when the engine is initialized for the first time. The render system provider
        /// attached to the engine is the sender.
        /// </summary>
        public static event EventHandler EngineInitialized;

        /// <summary>
        /// Event for when the engine is re-initialized, meaning an existing render system is attached when
        /// another render system is initialized. The render system provider attached to the engine is
        /// the sender.
        /// </summary>
        public static event EventHandler EngineReinitialized;

        /// <summary>
        /// Event for when the engine resources are disposed of.
        /// </summary>
        public static event EventHandler EngineDestroyed;

        /// <summary>
        /// Gets the engine service provider collection.
        /// </summary>
        public static EngineServiceProviders Services {
            get {
                return _providers;
            }
        }

        /// <summary>
        /// Gets the engine value map for binding and retrieving values for shader uniforms.
        /// </summary>
        public static EngineValueMap ValueMap {
            get {
                return _valueMap;
            }
        }

        /// <summary>
        /// Gets the render system provider. Equivalent to querying the engine service provider collection.
        /// </summary>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if the engine has not bee initialized with a render system.</exception>
        public static IRenderSystemProvider RenderSystemProvider {
            get {
                IRenderSystemProvider provider = (IRenderSystemProvider) _providers.GetService(_renderSystemType);
                if(provider == null) {
                    throw new TeslaException("Render system does not exist.");
                }
                return provider;
            }
        }


        /// <summary>
        /// Initializes the engine to use the provided render system provider.
        /// </summary>
        /// <param name="renderSystem">Render system to use</param>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if the render system is null.</exception>
        public static void Initialize(IRenderSystemProvider renderSystem) {
            if(renderSystem == null) {
                throw new TeslaException("Render system cannot be null.");
            }

            //If re-initing, destroy default render states.
            if(_providers.GetService(_renderSystemType) != null) {
                DestroyDefaultRenderStates();
                _providers.RemoveService(_renderSystemType);

                //Add render system to service map and create default render states.
                _providers.AddService(_renderSystemType, renderSystem);
                CreateDefaultRenderStates();

                //Clear the device to the default states.
                renderSystem.Renderer.ClearRenderStates();

                OnReinitialized();
                return;
            }

            //Add render system to service map and create default render states.
            _providers.AddService(_renderSystemType, renderSystem);
            CreateDefaultRenderStates();

            //Clear the device to the default states.
            renderSystem.Renderer.ClearRenderStates();
            OnInitialized();
        }

        /// <summary>
        /// Creates the default render states.
        /// </summary>
        private static void CreateDefaultRenderStates() {
            //*********************//
            //* Rasterizer States *//
            //*********************//

            RasterizerState cullNone = new RasterizerState();
            cullNone.Cull = CullMode.None;
            cullNone.VertexWinding = VertexWinding.Clockwise;
            cullNone.BindRenderState();
            RasterizerState.CullNone = cullNone;

            RasterizerState cullBackCW = new RasterizerState();
            cullBackCW.Cull = CullMode.Back;
            cullBackCW.VertexWinding = VertexWinding.Clockwise;
            cullBackCW.BindRenderState();
            RasterizerState.CullBackClockwiseFront = cullBackCW;

            RasterizerState cullBackCCW = new RasterizerState();
            cullBackCCW.Cull = CullMode.Back;
            cullBackCCW.VertexWinding = VertexWinding.CounterClockwise;
            cullBackCCW.BindRenderState();
            RasterizerState.CullBackCounterClockwiseFront = cullBackCCW;

            RasterizerState cullNoneWireframe = new RasterizerState();
            cullNoneWireframe.Cull = CullMode.None;
            cullNoneWireframe.Fill = FillMode.WireFrame;
            cullNoneWireframe.VertexWinding = VertexWinding.Clockwise;
            cullNoneWireframe.BindRenderState();
            RasterizerState.CullNoneWireframe = cullNoneWireframe;

            //****************//
            //* Blend States *//
            //****************//
            BlendState opaque = new BlendState();
            opaque.SetBlendEnable(0, false);
            opaque.BindRenderState();
            BlendState.Opaque = opaque;

            BlendState pmAlphaBlend = new BlendState();
            pmAlphaBlend.AlphaSourceBlend = Blend.One;
            pmAlphaBlend.AlphaDestinationBlend = Blend.InverseSourceAlpha;
            pmAlphaBlend.ColorSourceBlend = Blend.One;
            pmAlphaBlend.ColorDestinationBlend = Blend.InverseSourceAlpha;
            pmAlphaBlend.BindRenderState();
            BlendState.AlphaBlendPremultiplied = pmAlphaBlend;

            BlendState npmAlphaBlend = new BlendState();
            npmAlphaBlend.AlphaSourceBlend = Blend.SourceAlpha;
            npmAlphaBlend.AlphaDestinationBlend = Blend.InverseSourceAlpha;
            npmAlphaBlend.ColorSourceBlend = Blend.SourceAlpha;
            npmAlphaBlend.ColorDestinationBlend = Blend.InverseSourceAlpha;
            npmAlphaBlend.BindRenderState();
            BlendState.AlphaBlendNonPremultiplied = npmAlphaBlend;

            BlendState additive = new BlendState();
            additive.AlphaSourceBlend = Blend.SourceAlpha;
            additive.AlphaDestinationBlend = Blend.One;
            additive.ColorSourceBlend = Blend.SourceAlpha;
            additive.ColorDestinationBlend = Blend.One;
            additive.BindRenderState();
            BlendState.AdditiveBlend = additive;

            //***********************//
            //* DepthStencil States *//
            //***********************//
            DepthStencilState none = new DepthStencilState();
            none.DepthEnable = false;
            none.DepthWriteEnable = false;
            none.BindRenderState();
            DepthStencilState.None = none;

            DepthStencilState depthWriteOff = new DepthStencilState();
            depthWriteOff.DepthEnable = true;
            depthWriteOff.DepthWriteEnable = false;
            depthWriteOff.BindRenderState();
            DepthStencilState.DepthWriteOff = depthWriteOff;

            DepthStencilState def = new DepthStencilState();
            def.DepthEnable = true;
            def.DepthWriteEnable = true;
            def.BindRenderState();
            DepthStencilState.Default = def;


            //******************//
            //* Sampler States *//
            //******************//
            SamplerState pw = new SamplerState();
            pw.Filter = TextureFilter.Point;
            pw.AddressU = pw.AddressV = pw.AddressW = TextureAddressMode.Wrap;
            pw.BindRenderState();
            SamplerState.PointWrap = pw;

            SamplerState pc = new SamplerState();
            pc.Filter = TextureFilter.Point;
            pc.AddressU = pc.AddressV = pc.AddressW = TextureAddressMode.Clamp;
            pc.BindRenderState();
            SamplerState.PointClamp = pc;

            SamplerState lw = new SamplerState();
            lw.Filter = TextureFilter.Linear;
            lw.AddressU = lw.AddressV = lw.AddressW = TextureAddressMode.Wrap;
            lw.BindRenderState();
            SamplerState.LinearWrap = lw;

            SamplerState lc = new SamplerState();
            lc.Filter = TextureFilter.Linear;
            lc.AddressU = lc.AddressV = lc.AddressW = TextureAddressMode.Clamp;
            lc.BindRenderState();
            SamplerState.LinearClamp = lc;

            SamplerState aw = new SamplerState();
            aw.Filter = TextureFilter.Anisotropic;
            aw.AddressU = aw.AddressV = aw.AddressW = TextureAddressMode.Wrap;
            aw.BindRenderState();
            SamplerState.AnisotropicWrap = aw;

            SamplerState ac = new SamplerState();
            ac.Filter = TextureFilter.Anisotropic;
            ac.AddressU = ac.AddressV = ac.AddressW = TextureAddressMode.Clamp;
            ac.BindRenderState();
            SamplerState.AnisotropicClamp = ac;
        }

        /// <summary>
        /// Destroys the default render states.
        /// </summary>
        private static void DestroyDefaultRenderStates() {
            //*********************//
            //* Rasterizer States *//
            //*********************//

            if(RasterizerState.CullNone != null) {
                RasterizerState.CullNone.Dispose();
                RasterizerState.CullNone = null;
            }

            if(RasterizerState.CullBackClockwiseFront != null) {
                RasterizerState.CullBackClockwiseFront.Dispose();
                RasterizerState.CullBackClockwiseFront = null; 
            }

            if(RasterizerState.CullBackCounterClockwiseFront != null) {
                RasterizerState.CullBackCounterClockwiseFront.Dispose();
                RasterizerState.CullBackCounterClockwiseFront = null; 
            }

            if(RasterizerState.CullNoneWireframe != null) {
                RasterizerState.CullNoneWireframe.Dispose();
                RasterizerState.CullNoneWireframe = null; 
            }

            //****************//
            //* Blend States *//
            //****************//

            if(BlendState.Opaque != null) {
                BlendState.Opaque.Dispose();
                BlendState.Opaque = null; 
            }

            if(BlendState.AlphaBlendPremultiplied != null) {
                BlendState.AlphaBlendPremultiplied.Dispose();
                BlendState.AlphaBlendPremultiplied = null; 
            }

            if(BlendState.AlphaBlendNonPremultiplied != null) {
                BlendState.AlphaBlendNonPremultiplied.Dispose();
                BlendState.AlphaBlendNonPremultiplied = null; 
            }

            if(BlendState.AdditiveBlend != null) {
                BlendState.AdditiveBlend.Dispose();
                BlendState.AdditiveBlend = null; 
            }

            //***********************//
            //* DepthStencil States *//
            //***********************//

            if(DepthStencilState.None != null) {
                DepthStencilState.None.Dispose();
                DepthStencilState.None = null; 
            }

            if(DepthStencilState.DepthWriteOff != null) {
                DepthStencilState.DepthWriteOff.Dispose();
                DepthStencilState.DepthWriteOff = null; 
            }

            if(DepthStencilState.Default != null) {
                DepthStencilState.Default.Dispose();
                DepthStencilState.Default = null; 
            }

            //******************//
            //* Sampler States *//
            //******************//

            if(SamplerState.PointWrap != null) {
                SamplerState.PointWrap.Dispose();
                SamplerState.PointWrap = null; 
            }

            if(SamplerState.PointClamp != null) {
                SamplerState.PointClamp.Dispose();
                SamplerState.PointClamp = null; 
            }

            if(SamplerState.LinearWrap != null) {
                SamplerState.LinearWrap.Dispose();
                SamplerState.LinearWrap = null; 
            }

            if(SamplerState.LinearClamp != null) {
                SamplerState.LinearClamp.Dispose();
                SamplerState.LinearClamp = null; 
            }

            if(SamplerState.AnisotropicWrap != null) {
                SamplerState.AnisotropicWrap.Dispose();
                SamplerState.AnisotropicWrap = null; 
            }

            if(SamplerState.AnisotropicClamp != null) {
                SamplerState.AnisotropicClamp.Dispose();
                SamplerState.AnisotropicClamp = null; 
            }
        }

        /// <summary>
        /// Destroys default resources, sets the device to its default state, and destroys any remaining resources
        /// provided by the service providers (e.g. graphics objects). All service providers are disposed of and removed.
        /// </summary>
        public static void CleanUpResources() {
            DestroyDefaultRenderStates();
            _providers.DisposeAndRemoveAll();
            OnDispose();
        }

        /// <summary>
        /// Called when initialized.
        /// </summary>
        private static void OnInitialized() {
            if(EngineInitialized != null) {
                EngineInitialized(RenderSystemProvider, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when re-initialized.
        /// </summary>
        private static void OnReinitialized() {
            if(EngineReinitialized != null) {
                EngineReinitialized(RenderSystemProvider, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when disposed.
        /// </summary>
        private static void OnDispose() {
            if(EngineDestroyed != null) {
                EngineDestroyed(null, EventArgs.Empty);
            }
        }

    }
}
